Explorez la synthèse audio et le traitement numérique du signal (DSP) avec Python. Générez des formes d'onde, appliquez des filtres et créez du son.
Libérer le Son : Une Plongée Profonde dans Python pour la Synthèse Audio et le Traitement Numérique du Signal
De la musique qui résonne dans vos écouteurs aux paysages sonores immersifs des jeux vidéo et aux assistants vocaux de nos appareils, l'audio numérique fait partie intégrante de la vie moderne. Mais vous êtes-vous déjà demandé comment ces sons sont créés ? Ce n'est pas de la magie ; c'est un mélange fascinant de mathématiques, de physique et d'informatique connu sous le nom de Traitement Numérique du Signal (DSP). Aujourd'hui, nous allons lever le voile et vous montrer comment exploiter la puissance de Python pour générer, manipuler et synthétiser le son à partir de zéro.
Ce guide s'adresse aux développeurs, aux scientifiques des données, aux musiciens, aux artistes et à toute personne curieuse de l'intersection du code et de la créativité. Vous n'avez pas besoin d'être un expert en DSP ou un ingénieur audio chevronné. Avec une compréhension de base de Python, vous serez bientôt capable de créer vos propres paysages sonores uniques. Nous explorerons les éléments constitutifs fondamentaux de l'audio numérique, générerons des formes d'onde classiques, les façonnerons avec des enveloppes et des filtres, et construirons même un mini-synthétiseur. Commençons notre voyage dans le monde vibrant de l'audio computationnel.
Comprendre les Éléments Constitutifs de l'Audio Numérique
Avant de pouvoir écrire une seule ligne de code, nous devons comprendre comment le son est représenté dans un ordinateur. Dans le monde physique, le son est une onde de pression analogique continue. Les ordinateurs, étant numériques, ne peuvent pas stocker une onde continue. Au lieu de cela, ils prennent des milliers d'instantanés, ou échantillons, de l'onde chaque seconde. Ce processus est appelé échantillonnage.
Taux d'Échantillonnage
Le Taux d'Échantillonnage détermine combien d'échantillons sont pris par seconde. Il est mesuré en Hertz (Hz). Un taux d'échantillonnage plus élevé permet une représentation plus précise de l'onde sonore originale, conduisant à un son de plus haute fidélité. Les taux d'échantillonnage courants incluent :
- 44100 Hz (44.1 kHz) : La norme pour les CD audio. Il est choisi en fonction du théorème d'échantillonnage de Nyquist-Shannon, qui stipule que le taux d'échantillonnage doit être au moins le double de la fréquence la plus élevée que vous souhaitez capturer. Étant donné que la plage de l'audition humaine atteint environ 20 000 Hz, 44,1 kHz offre une marge de sécurité suffisante.
- 48000 Hz (48 kHz) : La norme pour la vidéo professionnelle et les stations de travail audio numériques (DAW).
- 96000 Hz (96 kHz) : Utilisé dans la production audio haute résolution pour une précision encore plus grande.
Pour nos besoins, nous utiliserons principalement 44100 Hz, car il offre un excellent équilibre entre qualité et efficacité de calcul.
Profondeur de Bits
Si le taux d'échantillonnage détermine la résolution temporelle, la Profondeur de Bits détermine la résolution en amplitude (volume). Chaque échantillon est un nombre qui représente l'amplitude de l'onde à ce moment précis. La profondeur de bits est le nombre de bits utilisés pour stocker ce nombre. Une profondeur de bits plus élevée permet plus de valeurs d'amplitude possibles, ce qui se traduit par une plage dynamique plus grande (la différence entre les sons les plus faibles et les plus forts possibles) et un plancher de bruit plus bas.
- 16 bits : La norme pour les CD, offrant 65 536 niveaux d'amplitude possibles.
- 24 bits : La norme pour la production audio professionnelle, offrant plus de 16,7 millions de niveaux.
Lorsque nous générons de l'audio en Python à l'aide de bibliothèques comme NumPy, nous travaillons généralement avec des nombres à virgule flottante (par exemple, entre -1,0 et 1,0) pour une précision maximale. Ceux-ci sont ensuite convertis en une profondeur de bits spécifique (comme des entiers 16 bits) lors de l'enregistrement dans un fichier ou de la lecture via le matériel.
Canaux
Ceci fait simplement référence au nombre de flux audio. L'audio Mono a un canal, tandis que l'audio Stéréo en a deux (gauche et droite), créant un sentiment d'espace et de directionnalité.
Configuration de Votre Environnement Python
Pour commencer, nous avons besoin de quelques bibliothèques Python essentielles. Elles forment notre boîte à outils pour le calcul numérique, le traitement du signal, la visualisation et la lecture audio.
Vous pouvez les installer avec pip :
pip install numpy scipy matplotlib sounddevice
Examinons brièvement leurs rôles :
- NumPy : La pierre angulaire du calcul scientifique en Python. Nous l'utiliserons pour créer et manipuler des tableaux de nombres, qui représenteront nos signaux audio.
- SciPy : Construit sur NumPy, il fournit une vaste collection d'algorithmes de traitement du signal, y compris la génération de formes d'onde et le filtrage.
- Matplotlib : La principale bibliothèque de traçage en Python. Elle est inestimable pour visualiser nos formes d'onde et comprendre les effets de notre traitement.
- SoundDevice : Une bibliothèque pratique pour lire nos tableaux NumPy sous forme audio via les haut-parleurs de votre ordinateur. Elle fournit une interface simple et multiplateforme.
Génération de Formes d'Onde : Le Cœur de la Synthèse
Tous les sons, aussi complexes soient-ils, peuvent être décomposés en combinaisons de formes d'onde simples et fondamentales. Ce sont les couleurs primaires de notre palette sonore. Apprenons à les générer.
L'Onde SinusoĂŻdale : Le Son le Plus Pur
L'onde sinusoïdale est l'élément constitutif absolu de tout son. Elle représente une seule fréquence sans harmoniques ni fondamentales supérieures. Elle sonne très douce, claire et est souvent décrite comme "sonnant comme une flûte". La formule mathématique est :
y(t) = Amplitude * sin(2 * π * fréquence * t)
OĂą 't' est le temps. Traduisons cela en code Python.
import numpy as np
import sounddevice as sd
import matplotlib.pyplot as plt
# --- Paramètres Globaux ---
SAMPLE_RATE = 44100 # échantillons par seconde
DURATION = 3.0 # secondes
# --- Génération de Forme d'Onde ---
def generate_sine_wave(frequency, duration, sample_rate, amplitude=0.5):
"""Génère une onde sinusoïdale.
Args:
frequency (float): La fréquence de l'onde sinusoïdale en Hz.
duration (float): La durée de l'onde en secondes.
sample_rate (int): Le taux d'échantillonnage en Hz.
amplitude (float): L'amplitude de l'onde (0.0 Ă 1.0).
Returns:
np.ndarray: L'onde sinusoïdale générée sous forme de tableau NumPy.
"""
# Crée un tableau de points temporels
t = np.linspace(0, duration, int(sample_rate * duration), False)
# Génère l'onde sinusoïdale
# 2 * pi * frequency est la fréquence angulaire
wave = amplitude * np.sin(2 * np.pi * frequency * t)
return wave
# --- Exemple d'Utilisation ---
if __name__ == "__main__":
# Génère une onde sinusoïdale de 440 Hz (note La4)
frequency_a4 = 440.0
sine_wave = generate_sine_wave(frequency_a4, DURATION, SAMPLE_RATE)
print("Lecture de l'onde sinusoĂŻdale de 440 Hz...")
# Joue le son
sd.play(sine_wave, SAMPLE_RATE)
sd.wait() # Attend que le son ait fini de jouer
print("Lecture terminée.")
# --- Visualisation ---
# Trace une petite partie de l'onde pour voir sa forme
plt.figure(figsize=(12, 4))
plt.plot(sine_wave[:500])
plt.title("Onde SinusoĂŻdale (440 Hz)")
plt.xlabel("Échantillon")
plt.ylabel("Amplitude")
plt.grid(True)
plt.show()
Dans ce code, np.linspace crée un tableau représentant l'axe temporel. Nous appliquons ensuite la fonction sinus à ce tableau temporel, mis à l'échelle par la fréquence désirée. Le résultat est un tableau NumPy où chaque élément est un échantillon de notre onde sonore. Nous pouvons ensuite le jouer avec sounddevice et le visualiser avec matplotlib.
Exploration d'Autres Formes d'Onde Fondamentales
Bien que l'onde sinusoïdale soit pure, elle n'est pas toujours la plus intéressante. D'autres formes d'onde de base sont riches en harmoniques, leur donnant un caractère (timbre) plus complexe et lumineux. Le module scipy.signal fournit des fonctions pratiques pour les générer.
Onde Carrée
Une onde carrée saute instantanément entre ses amplitudes maximale et minimale. Elle ne contient que des harmoniques d'ordre impair. Elle a un son brillant, nasillard et quelque peu "creux" ou "numérique", souvent associé à la musique des premiers jeux vidéo.
from scipy import signal
# Génère une onde carrée
square_wave = 0.5 * signal.square(2 * np.pi * 440 * np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False))
# sd.play(square_wave, SAMPLE_RATE)
# sd.wait()
Onde en Dent de Scie
Une onde en dent de scie augmente linéairement puis retombe instantanément à sa valeur minimale (ou vice versa). Elle est incroyablement riche, contenant toutes les harmoniques entières (paires et impaires). Cela lui donne un son très brillant, bourdonnant, et c'est un excellent point de départ pour la synthèse soustractive, que nous aborderons plus tard.
# Génère une onde en dent de scie
sawtooth_wave = 0.5 * signal.sawtooth(2 * np.pi * 440 * np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False))
# sd.play(sawtooth_wave, SAMPLE_RATE)
# sd.wait()
Onde Triangle
Une onde triangle monte et descend linéairement. Comme une onde carrée, elle ne contient que des harmoniques d'ordre impair, mais leur amplitude diminue beaucoup plus rapidement. Cela lui donne un son plus doux et plus mélodieux qu'une onde carrée, plus proche d'une onde sinusoïdale mais avec un peu plus de "corps".
# Génère une onde triangle (une dent de scie avec une largeur de 0.5)
triangle_wave = 0.5 * signal.sawtooth(2 * np.pi * 440 * np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False), width=0.5)
# sd.play(triangle_wave, SAMPLE_RATE)
# sd.wait()
Bruit Blanc : Le Son du Hasard
Le bruit blanc est un signal qui contient une énergie égale à chaque fréquence. Il sonne comme du statique ou le "chhh" d'une cascade. Il est incroyablement utile dans la conception sonore pour créer des sons percussifs (comme des charlestons et des caisses claires) et des effets atmosphériques. Sa génération est remarquablement simple.
# Génère du bruit blanc
num_samples = int(SAMPLE_RATE * DURATION)
white_noise = np.random.uniform(-1, 1, num_samples)
# sd.play(white_noise, SAMPLE_RATE)
# sd.wait()
Synthèse Additive : Construire la Complexité
Le mathématicien français Joseph Fourier a découvert que toute forme d'onde complexe et périodique peut être décomposée en une somme d'ondes sinusoïdales simples. C'est le fondement de la synthèse additive. En ajoutant des ondes sinusoïdales de fréquences (harmoniques) et d'amplitudes différentes, nous pouvons construire de nouveaux timbres plus riches.
Créons un son plus complexe en ajoutant les premières harmoniques d'une fréquence fondamentale.
def generate_complex_tone(fundamental_freq, duration, sample_rate):
t = np.linspace(0, duration, int(sample_rate * duration), False)
# Commence avec la fréquence fondamentale
tone = 0.5 * np.sin(2 * np.pi * fundamental_freq * t)
# Ajoute des harmoniques (fondamentales supérieures)
# 2ème harmonique (octave plus haut), amplitude plus faible
tone += 0.25 * np.sin(2 * np.pi * (2 * fundamental_freq) * t)
# 3ème harmonique, amplitude encore plus faible
tone += 0.12 * np.sin(2 * np.pi * (3 * fundamental_freq) * t)
# 5ème harmonique
tone += 0.08 * np.sin(2 * np.pi * (5 * fundamental_freq) * t)
# Normalise la forme d'onde pour qu'elle soit comprise entre -1 et 1
tone = tone / np.max(np.abs(tone))
return tone
# --- Exemple d'Utilisation ---
complex_tone = generate_complex_tone(220, DURATION, SAMPLE_RATE)
sd.play(complex_tone, SAMPLE_RATE)
sd.wait()
En sélectionnant soigneusement quelles harmoniques ajouter et à quelles amplitudes, vous pouvez commencer à imiter les sons d'instruments réels. Cet exemple simple sonne déjà beaucoup plus riche et intéressant qu'une simple onde sinusoïdale.
Façonner le Son avec des Enveloppes (ADSR)
Jusqu'à présent, nos sons commencent et s'arrêtent brusquement. Ils ont un volume constant tout au long de leur durée, ce qui sonne très artificiel et robotique. Dans le monde réel, les sons évoluent dans le temps. Une note de piano a un début net et fort qui s'estompe rapidement, tandis qu'une note jouée au violon peut augmenter progressivement en volume. Nous contrôlons cette évolution dynamique à l'aide d'une enveloppe d'amplitude.
Le Modèle ADSR
Le type d'enveloppe le plus courant est l'enveloppe ADSR, qui a quatre phases :
- Attack (Attaque) : Le temps nécessaire au son pour passer du silence à son amplitude maximale. Une attaque rapide crée un son percussif et net (comme un coup de tambour). Une attaque lente crée un son doux et montant (comme une nappe de cordes).
- Decay (Déclin) : Le temps nécessaire au son pour passer du niveau d'attaque maximal au niveau de sustain.
- Sustain (Maintien) : Le niveau d'amplitude que le son maintient tant que la note est maintenue. C'est un niveau, pas un temps.
- Release (Relâchement) : Le temps nécessaire au son pour s'estomper du niveau de sustain au silence après que la note soit relâchée. Un relâchement long fait persister le son, comme une note de piano avec la pédale de sustain enfoncée.
Implémentation d'une Enveloppe ADSR en Python
Nous pouvons implémenter une fonction pour générer une enveloppe ADSR sous forme de tableau NumPy. Nous l'appliquons ensuite à notre forme d'onde par une simple multiplication élément par élément.
def adsr_envelope(duration, sample_rate, attack_time, decay_time, sustain_level, release_time):
num_samples = int(duration * sample_rate)
attack_samples = int(attack_time * sample_rate)
decay_samples = int(decay_time * sample_rate)
release_samples = int(release_time * sample_rate)
sustain_samples = num_samples - attack_samples - decay_samples - release_samples
if sustain_samples < 0:
# Si les temps sont trop longs, ajustez-les proportionnellement
total_time = attack_time + decay_time + release_time
attack_time, decay_time, release_time = \
attack_time/total_time*duration, decay_time/total_time*duration, release_time/total_time*duration
attack_samples = int(attack_time * sample_rate)
decay_samples = int(decay_time * sample_rate)
release_samples = int(release_time * sample_rate)
sustain_samples = num_samples - attack_samples - decay_samples - release_samples
# Génère chaque partie de l'enveloppe
attack = np.linspace(0, 1, attack_samples)
decay = np.linspace(1, sustain_level, decay_samples)
sustain = np.full(sustain_samples, sustain_level)
release = np.linspace(sustain_level, 0, release_samples)
return np.concatenate([attack, decay, sustain, release])
# --- Exemple d'Utilisation : Son Plucky vs. Pad ---
# Son pluck (attaque rapide, déclin rapide, pas de sustain)
pluck_envelope = adsr_envelope(DURATION, SAMPLE_RATE, 0.01, 0.2, 0.0, 0.5)
# Son pad (attaque lente, relâchement long)
pad_envelope = adsr_envelope(DURATION, SAMPLE_RATE, 0.5, 0.2, 0.7, 1.0)
# Génère une onde en dent de scie riche en harmoniques pour appliquer les enveloppes
saw_wave_for_env = generate_complex_tone(220, DURATION, SAMPLE_RATE)
# Applique les enveloppes
plucky_sound = saw_wave_for_env * pluck_envelope
pad_sound = saw_wave_for_env * pad_envelope
print("Lecture du son plucky...")
sd.play(plucky_sound, SAMPLE_RATE)
sd.wait()
print("Lecture du son pad...")
sd.play(pad_sound, SAMPLE_RATE)
sd.wait()
# Visualisation des enveloppes
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plt.plot(pluck_envelope)
plt.title("Enveloppe ADSR Pluck")
plt.subplot(2, 1, 2)
plt.plot(pad_envelope)
plt.title("Enveloppe ADSR Pad")
plt.tight_layout()
plt.show()
Remarquez à quel point le caractère de la même forme d'onde sous-jacente change radicalement simplement en appliquant une enveloppe différente. C'est une technique fondamentale dans la conception sonore.
Introduction au Filtrage Numérique (Synthèse Soustractive)
Alors que la synthèse additive construit le son en ajoutant des ondes sinusoïdales, la synthèse soustractive fonctionne de manière inverse. Nous commençons avec un signal riche en harmoniques (comme une onde en dent de scie ou un bruit blanc) et ensuite nous enlevons ou atténuons des fréquences spécifiques à l'aide de filtres. Ceci est analogue à un sculpteur qui commence avec un bloc de marbre et le taille pour révéler une forme.
Types de Filtres Clés
- Filtre Passe-Bas : C'est le filtre le plus courant en synthèse. Il permet aux fréquences en dessous d'un certain point de "coupure" de passer tout en atténuant les fréquences au-dessus. Il rend un son plus sombre, plus chaud ou plus étouffé.
- Filtre Passe-Haut : L'inverse d'un filtre passe-bas. Il permet aux fréquences au-dessus de la coupure de passer, supprimant les basses fréquences et les basses. Il rend un son plus fin ou plus criard.
- Filtre Passe-Bande : Ne laisse passer qu'une bande de fréquences spécifique, coupant à la fois les aigus et les graves. Cela peut créer un effet "téléphone" ou "radio".
- Filtre Coupe-Bande (Notch) : L'inverse d'un filtre passe-bande. Il supprime une bande de fréquences spécifique.
Implémentation de Filtres avec SciPy
La bibliothèque scipy.signal fournit des outils puissants pour concevoir et appliquer des filtres numériques. Nous utiliserons un type courant appelé filtre de Butterworth, connu pour sa réponse plate dans la bande passante.
Le processus implique deux étapes : d'abord, concevoir le filtre pour obtenir ses coefficients, et ensuite, appliquer ces coefficients à notre signal audio.
from scipy.signal import butter, lfilter, freqz
def butter_lowpass_filter(data, cutoff, fs, order=5):
"""Applique un filtre de Butterworth passe-bas Ă un signal."""
nyquist = 0.5 * fs
normal_cutoff = cutoff / nyquist
# Obtient les coefficients du filtre
b, a = butter(order, normal_cutoff, btype='low', analog=False)
y = lfilter(b, a, data)
return y
# --- Exemple d'Utilisation ---
# Commence avec un signal riche : onde en dent de scie
saw_wave_rich = 0.5 * signal.sawtooth(2 * np.pi * 220 * np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False))
print("Lecture de l'onde en dent de scie originale...")
sd.play(saw_wave_rich, SAMPLE_RATE)
sd.wait()
# Applique un filtre passe-bas avec une coupure de 800 Hz
filtered_saw = butter_lowpass_filter(saw_wave_rich, cutoff=800, fs=SAMPLE_RATE, order=6)
print("Lecture de l'onde en dent de scie filtrée...")
sd.play(filtered_saw, SAMPLE_RATE)
sd.wait()
# --- Visualisation de la réponse en fréquence du filtre ---
cutoff_freq = 800
order = 6
b, a = butter(order, cutoff_freq / (0.5 * SAMPLE_RATE), btype='low')
w, h = freqz(b, a, worN=8000)
plt.figure(figsize=(10, 5))
plt.plot(0.5 * SAMPLE_RATE * w / np.pi, np.abs(h), 'b')
plt.plot(cutoff_freq, 0.5 * np.sqrt(2), 'ko')
plt.axvline(cutoff_freq, color='k', linestyle='--')
plt.xlim(0, 5000)
plt.title("Réponse en Fréquence du Filtre Passe-Bas")
plt.xlabel('Fréquence [Hz]')
plt.grid()
plt.show()
Écoutez la différence entre l'onde originale et l'onde filtrée. L'originale est brillante et bourdonnante ; la version filtrée est beaucoup plus douce et sombre car les harmoniques de haute fréquence ont été supprimées. Le balayage de la fréquence de coupure d'un filtre passe-bas est l'une des techniques les plus expressives et les plus courantes de la musique électronique.
Modulation : Ajouter du Mouvement et de la Vie
Les sons statiques sont ennuyeux. La modulation est la clé pour créer des sons dynamiques, évolutifs et intéressants. Le principe est simple : utiliser un signal (le modulateur) pour contrôler un paramètre d'un autre signal (le porteur). Un modulateur courant est un Oscillateur Basse Fréquence (LFO), qui n'est qu'un oscillateur avec une fréquence inférieure à la plage de l'audition humaine (par exemple, 0,1 Hz à 20 Hz).
Modulation d'Amplitude (AM) et Tremolo
C'est lorsque nous utilisons un LFO pour contrôler l'amplitude de notre son. Le résultat est un battement rythmique dans le volume, connu sous le nom de tremolo.
# Onde porteuse (le son que nous entendons)
carrier_freq = 300
carrier = generate_sine_wave(carrier_freq, DURATION, SAMPLE_RATE)
# LFO modulateur (contrĂ´le le volume)
lfo_freq = 5 # LFO de 5 Hz
modulator = generate_sine_wave(lfo_freq, DURATION, SAMPLE_RATE, amplitude=1.0)
# Crée un effet de tremolo
# Nous mettons à l'échelle le modulateur pour qu'il soit de 0 à 1
tremolo_modulator = (modulator + 1) / 2
tremolo_sound = carrier * tremolo_modulator
print("Lecture de l'effet tremolo...")
sd.play(tremolo_sound, SAMPLE_RATE)
sd.wait()
Modulation de Fréquence (FM) et Vibrato
C'est lorsque nous utilisons un LFO pour contrôler la fréquence de notre son. Une modulation subtile et lente de la fréquence crée un vibrato, le léger changement de hauteur que les chanteurs et les violonistes utilisent pour ajouter de l'expression.
# Crée un effet de vibrato
t = np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False)
carrier_freq = 300
lfo_freq = 7
modulation_depth = 10 # De combien la fréquence variera
# Le LFO sera ajouté à la fréquence porteuse
modulator_vibrato = modulation_depth * np.sin(2 * np.pi * lfo_freq * t)
# La fréquence instantanée change avec le temps
instantaneous_freq = carrier_freq + modulator_vibrato
# Nous devons intégrer la fréquence pour obtenir la phase
phase = np.cumsum(2 * np.pi * instantaneous_freq / SAMPLE_RATE)
vibrato_sound = 0.5 * np.sin(phase)
print("Lecture de l'effet vibrato...")
sd.play(vibrato_sound, SAMPLE_RATE)
sd.wait()
Il s'agit d'une version simplifiée de la synthèse FM. Lorsque la fréquence du LFO est augmentée dans la plage audible, elle crée des fréquences latérales complexes, résultant en des sons riches, ressemblant à des cloches et métalliques. C'est la base du son emblématique de synthétiseurs comme le Yamaha DX7.
Assemblage : Un Projet de Mini Synthétiseur
Combinons tout ce que nous avons appris dans une classe de synthétiseur simple et fonctionnelle. Cela encapsulera notre oscillateur, notre enveloppe et notre filtre dans un seul objet réutilisable.
class MiniSynth:
def __init__(self, sample_rate=44100):
self.sample_rate = sample_rate
def generate_note(self, frequency, duration, waveform='sine',
adsr_params=(0.05, 0.2, 0.5, 0.3),
filter_params=None):
"""Génère une note synthétisée unique."""
num_samples = int(duration * self.sample_rate)
t = np.linspace(0, duration, num_samples, False)
# 1. Oscillateur
if waveform == 'sine':
wave = np.sin(2 * np.pi * frequency * t)
elif waveform == 'square':
wave = signal.square(2 * np.pi * frequency * t)
elif waveform == 'sawtooth':
wave = signal.sawtooth(2 * np.pi * frequency * t)
elif waveform == 'triangle':
wave = signal.sawtooth(2 * np.pi * frequency * t, width=0.5)
else:
raise ValueError("Forme d'onde non supportée")
# 2. Enveloppe
attack, decay, sustain, release = adsr_params
envelope = adsr_envelope(duration, self.sample_rate, attack, decay, sustain, release)
# Assure que l'enveloppe et l'onde ont la mĂŞme longueur
min_len = min(len(wave), len(envelope))
wave = wave[:min_len] * envelope[:min_len]
# 3. Filtre (optionnel)
if filter_params:
cutoff = filter_params.get('cutoff', 1000)
order = filter_params.get('order', 5)
filter_type = filter_params.get('type', 'low')
if filter_type == 'low':
wave = butter_lowpass_filter(wave, cutoff, self.sample_rate, order)
# ... pourrait ajouter passe-haut etc. ici
# Normalise Ă une amplitude de 0.5
return wave * 0.5
# --- Exemple d'Utilisation du Synthé ---
synth = MiniSynth()
# Un son de basse brillant et plucky
bass_note = synth.generate_note(
frequency=110, # Note La2
duration=1.5,
waveform='sawtooth',
adsr_params=(0.01, 0.3, 0.0, 0.2),
filter_params={'cutoff': 600, 'order': 6}
)
print("Lecture de la note de basse du synthé...")
sd.play(bass_note, SAMPLE_RATE)
sd.wait()
# Un son de nappe doux et atmosphérique
pad_note = synth.generate_note(
frequency=440, # Note La4
duration=5.0,
waveform='triangle',
adsr_params=(1.0, 0.5, 0.7, 1.5)
)
print("Lecture de la note de nappe du synthé...")
sd.play(pad_note, SAMPLE_RATE)
sd.wait()
# Une simple mélodie
melody = [
('C4', 261.63, 0.4),
('D4', 293.66, 0.4),
('E4', 329.63, 0.4),
('C4', 261.63, 0.8)
]
final_melody = []
for note, freq, dur in melody:
sound = synth.generate_note(freq, dur, 'square', adsr_params=(0.01, 0.1, 0.2, 0.1), filter_params={'cutoff': 1500})
final_melody.append(sound)
full_melody_wave = np.concatenate(final_melody)
print("Lecture d'une courte mélodie...")
sd.play(full_melody_wave, SAMPLE_RATE)
sd.wait()
Cette classe simple est une démonstration puissante des principes que nous avons abordés. Je vous encourage à l'expérimenter. Essayez différentes formes d'onde, ajustez les paramètres ADSR et modifiez la coupure du filtre pour voir à quel point vous pouvez modifier radicalement le son.
Au-delĂ des Bases : OĂą Aller Ensuite ?
Nous n'avons fait qu'effleurer la surface du domaine profond et gratifiant de la synthèse audio et du DSP. Si cela a suscité votre intérêt, voici quelques sujets avancés à explorer :
- Synthèse par Table d'Ondes : Au lieu d'utiliser des formes mathématiquement parfaites, cette technique utilise des formes d'onde pré-enregistrées, à cycle unique, comme source d'oscillateur, permettant des timbres incroyablement complexes et évolutifs.
- Synthèse Granulaire : Crée de nouveaux sons en décomposant un échantillon audio existant en minuscules fragments (grains), puis en les réarrangeant, en les étirant et en les accordant. C'est fantastique pour créer des textures atmosphériques et des nappes.
- Synthèse par Modélisation Physique : Une approche fascinante qui tente de créer du son en modélisant mathématiquement les propriétés physiques d'un instrument – la corde d'une guitare, le tube d'une clarinette, la membrane d'un tambour.
- Traitement Audio en Temps Réel : Des bibliothèques comme PyAudio et SoundCard vous permettent de travailler avec des flux audio provenant de microphones ou d'autres entrées en temps réel, ouvrant la porte aux effets en direct, aux installations interactives et plus encore.
- Apprentissage Automatique dans l'Audio : L'IA et l'apprentissage profond révolutionnent l'audio. Les modèles peuvent générer de la musique nouvelle, synthétiser une parole humaine réaliste, ou même séparer des instruments individuels d'une chanson mixée.
Conclusion
Nous avons parcouru la nature fondamentale du son numérique jusqu'à la construction d'un synthétiseur fonctionnel. Nous avons appris à générer des formes d'onde pures et complexes à l'aide de Python, NumPy et SciPy. Nous avons découvert comment donner vie et forme à nos sons à l'aide d'enveloppes ADSR, sculpter leur caractère avec des filtres numériques, et ajouter un mouvement dynamique avec la modulation. Le code que nous avons écrit n'est pas seulement un exercice technique ; c'est un outil créatif.
La puissante pile scientifique de Python en fait une plateforme exceptionnelle pour apprendre, expérimenter et créer dans le monde de l'audio. Que votre objectif soit de créer un effet sonore personnalisé pour un projet, de construire un instrument de musique, ou simplement de comprendre la technologie derrière les sons que vous entendez chaque jour, les principes que vous avez appris ici sont votre point de départ. Maintenant, c'est à vous d'expérimenter. Commencez à combiner ces techniques, essayez de nouveaux paramètres et écoutez attentivement les résultats. Le vaste univers du son est maintenant à portée de main – qu'allez-vous créer ?